﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XingLucifer.IBase;
using XingLucifer.IBase.Enums;

namespace XingLucifer.Devices
{
    public class SerialPortCom : ICommunication
    {
        public SerialPortCom(byte suffixIP, string ip, int port, Parity parity, int dataBits, StopBits stopBits, DeviceTypes deviceTypes, 
            int index, TaskTypes taskType, string serviceName, CancellationTokenSource token, EndianTypes endianTypes, bool dtr = false, bool rts = false)
        {
            _ip = ip;
            _port = port;
            _deviceTypes = deviceTypes;
            _suffixIP = suffixIP;
            _index = index;
            _cacheByte = new List<byte>();
            _endianTypes = endianTypes;
            _socketToken = token;
            _taskType = taskType;
            _serviceName = serviceName;
            Timeout = 1000;
            _serialPort = new SerialPort(_ip, _port, parity, dataBits, stopBits);
            _serialPort.ReadBufferSize = 1024;
            _serialPort.WriteBufferSize = 1024;
            _serialPort.DtrEnable = dtr;
            _serialPort.RtsEnable = rts;
        }

        #region 属性
        private Stopwatch _stopwatch = new Stopwatch();
        protected System.Net.Sockets.Socket _socket;
        protected CancellationTokenSource _socketToken;
        protected EndianTypes _endianTypes;
        protected byte _suffixIP;
        private string _ip;
        private int _port;
        private DeviceTypes _deviceTypes;
        private int _index;
        protected List<byte> _cacheByte;
        protected IBase.IProtocol _protocol;
        private TaskTypes _taskType;
        private string _serviceName;

        public string IP => _ip;
        public int Port => _port;
        public DeviceTypes DeviceType => _deviceTypes;
        public int Index => _index;
        public int Timeout { get; set; }

        public Func<ICommunication, int, string, int, DeviceTypes, TaskTypes, string, CancellationTokenSource, bool> ReconnectDisconnection { get; set; }
        public TaskTypes TaskType { get => _taskType; }
        public string ServiceName { get => _serviceName; }

        public EndianTypes EndianType => _endianTypes;

        private volatile int _lock = 0;
        #endregion
        #region Synchronize
        protected void Locked()
        {
            int lockNumber = 0;
            while (Interlocked.Exchange(ref _lock, 1) != 0)
            {
                _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0;
                if (++lockNumber > 50)
                {
                    Thread.Sleep(1);
                    lockNumber = 0;
                }
            }
        }

        protected void UnLock() => Interlocked.Exchange(ref _lock, 0);
        #endregion

        public void Close()
        {
            if (_serialPort.IsOpen && _serialPort != null)
            {
                _serialPort.Close();
            }
        }

        private SerialPort _serialPort;
        public bool Open()
        {
            if (!_serialPort.IsOpen)
            {
                try
                {
                    _serialPort.Open();
                }
                catch (Exception)
                {
                    return false;
                }
            }
            return _serialPort.IsOpen;
        }

        public byte[] Read(int length)
        {
            _stopwatch.Restart();
            byte[] data = new byte[1024];
            int offset = 0;
            while (offset < length)
            {
                if (_stopwatch.ElapsedMilliseconds >= Timeout)
                {
                    throw new TimeoutException("等待数据返回超时");
                }
                offset += _serialPort.Read(data, offset, length - offset);
            }
            _stopwatch.Stop();
            return data.Take(length).ToArray();
        }

        public byte[] SendToRead(byte[] bytes, int count)
        {
            for (int i = 0; i < count; i++)
            {
                try
                {
                    bytes = _protocol.Serialize(bytes);
                    Locked();
                    _serialPort.Write(bytes, 0, bytes.Length);
                    Thread.Sleep(5);
                    List<byte> list = new List<byte>();
                    byte[] buffer = new byte[1024];
                    int length = _serialPort.Read(buffer, 0, 1024);
                    list.AddRange(buffer.Take(length));
                    while (!_protocol.Verify(list))
                    {
                        Thread.Sleep(5);
                        length = _serialPort.Read(buffer, 0, 1024);
                        list.AddRange(buffer.Take(length));
                    }
                    UnLock();
                    return _protocol.Deserialize(list);
                }
                catch (Exception ex)
                {
                    UnLock();
                    if (count == 1
                        || (_socketToken != null && _socketToken.Token.IsCancellationRequested)
                        || ex.ToString().Contains("远程主机关闭"))
                    {
                        throw ex;
                    }
                    if (_socketToken != null && !_socketToken.Token.IsCancellationRequested)
                    {
                        Thread.Sleep(100);
                        ReconnectDisconnection?.Invoke(this, Index, IP, Port, DeviceType, TaskType, ServiceName, _socketToken);
                    }
                }
            }
            return default;
        }

        public void SetProtocol(IProtocol protocol)
        {
            _protocol = protocol;
        }

        public bool Write(byte[] bytes)
        {
            _serialPort.Write(bytes, 0, bytes.Length);
            return true;
        }
    }
}
